﻿/*
Copyright © 2010 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Faz.Dependencies.Controls
{
    /// <summary>
    /// Places the element as part of layers that will be linked by arrows.
    /// </summary>
    public class LinkViewResult : Panel
    {
        /// <summary>
        /// Identifies the <see cref="LinkViewResult.Connectors"/> read-only property.
        /// </summary>
        public static readonly DependencyProperty ConnectorsProperty
            = DependencyProperty.RegisterReadOnly("Connectors", typeof(ICollection<LinkViewConnector>), typeof(LinkViewResult), new PropertyMetadata(new ObservableCollection<LinkViewConnector>())).DependencyProperty;

        /// <summary>
        /// Identifies the <see cref="LinkViewResult.InternalMargin"/> property.
        /// </summary>
        public static readonly DependencyProperty InternalMarginProperty =
            DependencyProperty.Register("InternalMargin", typeof(Size), typeof(LinkViewResult), new UIPropertyMetadata(new Size(10, 10)));

        /// <summary>
        /// The sizes of each level as computed by the MeasureOverride method.
        /// </summary>
        private readonly IDictionary<int, Size> sizes;

        /// <summary>
        /// Initializes a new instance of the LinkViewResult class.
        /// </summary>
        public LinkViewResult()
        {
            this.sizes = new SortedList<int, Size>();
        }

        /// <summary>
        /// Gets the connectors associated with the displayed elements.
        /// </summary>
        public ICollection<LinkViewConnector> Connectors
        {
            get { return (ICollection<LinkViewConnector>)this.GetValue(ConnectorsProperty); }
        }

        /// <summary>
        /// Gets or sets the minimal internal margin applied to the element.
        /// </summary>
        public Size InternalMargin
        {
            get { return (Size)GetValue(InternalMarginProperty); }
            set { SetValue(InternalMarginProperty, value); }
        }

        /// <summary>
        /// Gets the encapsulating <see cref="ListView"/> instance.
        /// </summary>
        protected ListView ListView
        {
            get { return this.TemplatedParent as ListView; }
        }

        /// <summary>
        /// Gets the associated <see cref="LinkView"/> instance.
        /// </summary>
        protected LinkView LinkView
        {
            get { return this.ListView.View as LinkView; }
        }

        /// <summary>
        /// Defines the sizes of each layer.
        /// </summary>
        /// <param name="availableSize">The available size.</param>
        /// <returns>The minimum size to draw the components.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            // Define the size of each level
            this.sizes.Clear();
            foreach (ContentControl child in this.Children)
            {
                object content = child.Content;
                int level = this.LinkView.RetrieveLevel(content);
                if (!this.sizes.ContainsKey(level))
                {
                    this.sizes.Add(level, new Size());
                }

                child.Measure(availableSize);
                this.sizes[level] = new Size(
                    this.sizes[level].Width + this.InternalMargin.Width + child.DesiredSize.Width,
                    Math.Max(this.sizes[level].Height, child.DesiredSize.Height));
            }

            // Extract all the existing content
            IDictionary<object, ContentControl> elements = new Dictionary<object, ContentControl>();
            foreach (ContentControl child in this.Children)
            {
                if (child.Content != null)
                {
                    elements.Add(child.Content, child);
                }
            }

            // Create all the required connectors
            this.Connectors.Clear();
            foreach (ContentControl child in this.Children)
            {
                if (child.Content == null)
                {
                    continue;
                }

                foreach (object dependency in this.LinkView.ExtractDependencies(child.Content))
                {
                    if (elements.ContainsKey(dependency))
                    {
                        LinkViewConnector connector = new LinkViewConnector();
                        connector.LinkView = this.LinkView;
                        connector.Source = elements[child.Content];
                        connector.Destination = elements[dependency];
                        this.Connectors.Add(connector);
                    }
                }
            }

            // Compute the minimal size for the panel
            double finalHeight = 0;
            double finalWidth = 0;
            foreach (Size size in this.sizes.Values)
            {
                finalHeight += size.Height + this.InternalMargin.Height;
                finalWidth = Math.Max(finalWidth, size.Width);
            }

            return new Size(finalWidth + this.InternalMargin.Width, finalHeight + this.InternalMargin.Height);
        }

        /// <summary>
        /// Places the components at their final positions.
        /// </summary>
        /// <param name="finalSize">The available size.</param>
        /// <returns>The used size - will be equal to <paramref name="finalSize"/>.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            // The variables used to position the elements
            IDictionary<int, double> currentX = new Dictionary<int, double>();
            IDictionary<int, double> currentY = new Dictionary<int, double>();

            // Compute the currentY values
            double minHeight = this.sizes.Values.Aggregate(0.0, (c, s) => c + s.Height);
            double step = (finalSize.Height - minHeight) / (this.sizes.Keys.Count + 1);
            step = Math.Max(step, this.InternalMargin.Height);

            double previousY = 0.0;
            double previousHeight = 0.0;
            foreach (KeyValuePair<int, Size> pair in this.sizes)
            {
                currentY[pair.Key] = previousY + previousHeight + step;
                previousY = currentY[pair.Key];
                previousHeight = pair.Value.Height;
            }

            // Place the elements
            foreach (ContentControl child in this.Children)
            {
                object content = child.Content;
                int level = this.LinkView.RetrieveLevel(content);
                if (!currentX.ContainsKey(level))
                {
                    currentX.Add(level, (finalSize.Width - this.sizes[level].Width) / 2.0);
                }

                child.Arrange(new Rect(new Point(currentX[level], currentY[level]), child.DesiredSize));
                currentX[level] = currentX[level] + this.InternalMargin.Width + child.DesiredSize.Width;
            }

            // Use all the available space
            return finalSize;
        }
    }
}
